-module(mails).
-export([mail_client/4, mail_client/5, mail_client/6]).

mail_client(Server, Port, User, Password, SendTo, MailBody) ->
	throw({mail_error, "not impl."}).

mail_client(Server, Port, User, Password, IsDel) ->
	case gen_tcp:connect(Server, Port, [binary]) of
		{ok, Socket} ->
			mail_response(Socket),
			mail_request(Socket, ["USER ", User, "\r\n"]),
			mail_response(Socket),
			mail_request(Socket, ["PASS ", Password, "\r\n"]),
			mail_response(Socket),
			MailList = mail_list(Socket),
			MailIndexList = [{X, Socket} || X <- MailList],
			Mails = lists:map(fun mail_mail/1, MailIndexList),
			case IsDel of
				true ->
					lists:map(fun mail_dele/1, MailIndexList);
				Other ->
					Other
			end,
			mail_request(Socket, ["QUIT\r\n"]),
			gen_tcp:close(Socket),
			{mail_ok, Mails};
		{error, Reason} ->
			throw({mail_error, Reason})
	end.

mail_client(Server, Port, User, Password) ->
	mail_client(Server, Port, User, Password, false).

mail_dele({Item, Socket}) ->
	mail_request(Socket, ["DELE ", Item, "\r\n"]),
	mail_response(Socket).

mail_content_transfer_encoding(Mail) ->
	case re:run(Mail, "\r\nContent-Transfer-Encoding:([^\r\n]*)") of
		{match, [{Start, Length}|_]} ->
			string:to_lower(string:strip(string:substr(Mail, Start + 29, Length - 28), both));
		nomatch ->
			"unknown encoding"
	end.

mail_body(Mail) ->
	case re:run(Mail, "\r\n[c|C]ontent-[t|T]ype:[ \r\n]*multipart/[^;]*;[ \r\n\t]*boundary=\"[^\"]*\"\r\n") of
		{match, [{Start, Length}|_]} ->
			ContentType = string:substr(Mail, Start, Length - 2),
			SplitPos = string:rstr(ContentType, "\""),
			Split = string:concat("--", string:substr(ContentType, SplitPos + 1, string:len(ContentType) - SplitPos)),
			PartStart = string:str(Mail, Split),
			if
				PartStart == 0 ->
					throw({mail_error, "get mail body fail."});
				true ->
					B = string:substr(Mail, PartStart + string:len(Split)),
					BodyStart = string:str(B, "\r\n\r\n"),
					if
						BodyStart == 0 ->
							throw({mail_error, "get mail body fail."});
						true ->
							BodyS = string:substr(B, BodyStart + 4),
							PartEnd = string:str(BodyS, Split),
							if
								PartEnd == 0 ->
									throw({mail_error, "get mail body fail."});
								true ->
									string:substr(BodyS, 1, PartEnd - 1)
							end
					end
			end;
		nomatch ->
			Pos = string:str(Mail, "\r\n\r\n"),
			if
				Pos == 0 ->
					throw({mail_error, "get mail body fail."});
				true ->
					BodyStart = string:substr(Mail, Pos + 4),
					PosEnd = string:str(BodyStart, "\r\n\r\n"),
					if
						PosEnd == 0 ->
							throw({mail_error, "get mail body fail."});
						true ->
							string:substr(BodyStart, 1, PosEnd - 1)
					end
			end
	end.

mail_mail({Item, Socket}) ->
	mail_request(Socket, ["RETR ", Item, "\r\n"]),
	{mail_ok, Response} = mail_block(Socket, [], 0),
	TransferEncoding = mail_content_transfer_encoding(Response),
	MailBody = mail_body(Response),
	if
		TransferEncoding == "base64" ->
			base64:mime_decode_to_string(list_to_binary(MailBody));
		true ->
			MailBody
	end.

mail_list(Socket) ->
	mail_request(Socket, ["LIST\r\n"]),
	{mail_ok, Response} = mail_block(Socket, [], 0),
	MailLists = string:tokens(Response, "\r\n"),
	IndexList = lists:map(fun list_map/1, MailLists),
	lists:filter(fun list_filter/1, IndexList).

list_map(Item) ->
	ItemState = string:left(Item, 3),
	if
		ItemState =:= ".\r\n" ->
			mail_null;
		ItemState =:= "+OK" ->
			mail_null;
		true ->
			Pos = string:str(Item, " "),
			if
				Pos =:= 0 ->
					mail_null;
				true ->
					string:substr(Item, 1, Pos-1)
			end
	end.

list_filter(Item) ->
	if
		Item =:= mail_null ->
			false;
		true ->
			true
	end.

mail_block(Socket, Response, 0) ->
	receive
		{tcp, Socket, Binary} ->
			Bin = lists:append(Response, binary_to_list(Binary)),
			Pos = string:str(Bin, "\r\n.\r\n"),
			mail_block(Socket, Bin, Pos);
		{tcp_closed, Socket} ->
			gen_tcp:close(Socket),
			throw({mail_error, "server disconnected."});
		{tcp_error, Socket, Reason} ->
			gen_tcp:close(Socket),
			throw({mail_error, Reason})
	end;
mail_block(_, Response, _) ->
	{mail_ok, Response}.

mail_request(SessionSocket, Cmd) ->
	case gen_tcp:send(SessionSocket, Cmd) of
		ok ->
			mail_ok;
		{error, Reason} ->
			throw({mail_error, Reason})
	end.

mail_response(SessionSocket) ->
	{mail_ok, Response} = mail_readline(SessionSocket, [], 0),
	ResponseState = string:left(Response, 3),
	if
		ResponseState =:= "+OK" ->
			mail_ok;
		ResponseState =:= "-ER" ->
			throw({mail_error, string:strip(string:substr(Response, 5), both)});
		true ->
			throw({mail_error, "unknown state."})
	end.

mail_readline(SessionSocket, Response, 0) ->
	receive
		{tcp, SessionSocket, Binary} ->
			Bin = lists:append(Response, binary_to_list(Binary)),
			Pos = string:str(binary_to_list(Binary), "\r\n"),
			mail_readline(SessionSocket, Bin, Pos);
		{tcp_closed, SessionSocket} ->
			gen_tcp:close(SessionSocket),
			throw({mail_error, "server disconnected."});
		{tcp_error, SessionSocket, Reason} ->
			gen_tcp:close(SessionSocket),
			throw({mail_error, Reason})
	end;
mail_readline(_, Response, _) ->
	{mail_ok, Response}.
